﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Documents;
using System.Windows;
using SDF.Pagination;

namespace SDF
{
    public class SmartDocumentPaginator : DocumentPaginator
    {

        #region Constructor

        public SmartDocumentPaginator(SmartDocument document)
        {
            Pages = new List<DocumentPage>();

            Document = document;
            PageSize = new Size(21 * MeasureUnits.Dpc, 29.7 * MeasureUnits.Dpc); // A4
            Margin = Document.CalculateActualMargin();
        }

        #endregion

        #region New Properties

        protected SmartDocument Document { get; private set; }

        protected List<DocumentPage> Pages { get; private set; }

        public Thickness Margin
        {
            get { return _Margin; }
            set
            {
                _Margin = value;
                RecalculateContentSize();
                OnPagesChanged(new PagesChangedEventArgs(0, PageCount));
            }
        }
        private Thickness _Margin;

        protected Rect BleedBox { get; private set; }

        protected Rect ContentBox { get; private set; }

        protected void RecalculateContentSize()
        {
            BleedBox = new Rect(PageSize);
            ContentBox = new Rect(PageSize).Subtract(Margin);
        }

        #endregion

        #region Overriden behaviour

        public override IDocumentPaginatorSource Source
        {
            get { return Document; }
        }

        public override Size PageSize
        {
            get { return _PageSize; }
            set
            {
                _PageSize = value;
                RecalculateContentSize();
                OnPagesChanged(new PagesChangedEventArgs(0, PageCount));
            }
        }
        private Size _PageSize;

        public override bool IsPageCountValid
        {
            get { return _IsPageCountValid; }
        }
        private bool _IsPageCountValid;

        public override int PageCount
        {
            get { return Pages.Count; }
        }

        protected override void OnPagesChanged(PagesChangedEventArgs e)
        {
            _IsPageCountValid = false;
            base.OnPagesChanged(e);
        }

        public override DocumentPage GetPage(int pageNumber)
        {
            if (!IsPageCountValid)
                Paginate();

            if (pageNumber >= PageCount)
                return null;

            return Pages[pageNumber];
        }

        #endregion

        #region Paginate Method

        private void Paginate()
        {
            Pages.Clear();

            double columnWidth = Document.ColumnWidth;
            // Calculate minimum column width. If not specified, assume 20 times the line height, like Microsoft does
            // http://msdn.microsoft.com/en-us/library/system.windows.documents.flowdocument.columnwidth.aspx
            if (double.IsNaN(columnWidth) || double.IsInfinity(columnWidth))
                columnWidth = 20 * Document.LineHeight;

            // Calculate the column gap. If not specified, assume 1 times the line height, like Microsoft does
            // http://msdn.microsoft.com/en-us/library/system.windows.documents.flowdocument.columngap.aspx
            double columnGap = Document.ColumnGap;
            if (double.IsNaN(columnGap) || double.IsInfinity(columnGap))
                columnGap = Document.LineHeight;

            // Find out how many columns we can fit
            int columns = 1;
            double aux = (columnWidth * columns) + (columnGap * (columns - 1));
            double totalWidth = aux;
            while (aux < this.ContentBox.Width)
            {
                totalWidth = aux;
                columns++;
                aux = (columnWidth * columns) + (columnGap * (columns - 1));
            }
            // When we get here, it is because can't fit the columns anymore (i.e. one column too many)
            if (columns > 1)
                columns--;

            // Distribute the remaining space if the column width is flexible
            if (Document.IsColumnWidthFlexible)
            {
                double remainingSpace = this.ContentBox.Width - totalWidth;
                columnWidth += remainingSpace / columns;
            }

            // This will be the size of a column
            Size columnSize = new Size(columnWidth, ContentBox.Height);

            // Render the document
            RenderDocument(columnWidth);

            // This will create DocumentPage instances and then go through the document contents cloning them 
            // and adding them to the DocumentPages as appropriate.
            var context = new HarvestingContext();
            var pages = new List<SmartPage>();
            SmartPage currentPage = null;

            var harvestResult = Document.Content.Harvest(context, ContentBox.Height, null);
            
            while (harvestResult.HarvestedBlock != null)
            {
                var content = harvestResult.HarvestedBlock as PageContent;

                if (currentPage == null)
                {
                    currentPage = new SmartPage(Document, PageSize, Margin, columns, columnWidth);
                    pages.Add(currentPage);

                    Guid sectionId = Guid.Empty;
                    var section = content.Section;
                    if (section != null)
                        sectionId = section.Guid;


                    // Update the PaginationInfo
                    if ((section != null) && (sectionId != context.CurrentSection))
                    {
                        if (section.ResetPageNumbers)
                            context.CurrentPage = content.Section.FirstPageNumber;

                    }

                    currentPage.PaginationInfo.PageNumber = context.CurrentPage;
                    currentPage.PaginationInfo.SectionGuid = context.CurrentSection;

                    if (!context.SectionsPageCount.ContainsKey(sectionId))
                    {
                        // We don't add (1). This is because if the section started counting at another number, like 7 for example,
                        // we don't want to say "Page 7 of 1".
                        context.SectionsPageCount.Add(sectionId, context.CurrentPage);
                    }
                    else
                    {
                        context.SectionsPageCount[sectionId] = context.SectionsPageCount[sectionId] + 1;
                    }

                    context.CurrentSection = sectionId;
                    context.CurrentPage++;
                }


                if (currentPage.AddContent(content))
                {
                    // Was last column, so break the page
                    currentPage = null;
                }
                else if (harvestResult.AddPageBreak)
                {
                    // Was last column, so break the page
                    currentPage = null;
                }

                // Get the next column
                harvestResult = Document.Content.Harvest(context, ContentBox.Height, null);
            }

            // Now that we harvested all the pages, we need to update the PaginationInfo.TotalPages property of every page
            // and then render the page 
            foreach (var page in pages)
            {
                var paginationInfo = page.PaginationInfo;
                if (paginationInfo.SectionGuid != Guid.Empty)
                    paginationInfo.TotalPages = context.SectionsPageCount[paginationInfo.SectionGuid];

                page.Render();
                Pages.Add(new DocumentPage(page, PageSize, BleedBox, ContentBox));
            }

            // Mark page count as valid
            _IsPageCountValid = true;
        }


        private void RenderDocument(double columnWidth)
        {
            Size pageSize = new Size(columnWidth, double.PositiveInfinity);
            Document.Content.Measure(pageSize);

            pageSize.Height = Math.Max(Document.Content.DesiredSize.Height, this.PageSize.Height);
            Rect pageBounds = new Rect(pageSize);
            Document.Content.Arrange(pageBounds);
        }

        #endregion

    }
}
